﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static LightCAD.MathLib.CSG.Mesh;

namespace LightCAD.MathLib.CSG
{

   class RemeshOptions
    {
        public double maxEdgeLength;
        public double minEdgeLength;
        public double minAngle;
        public double maxAngle;
        public RemeshOptions()
        {
            this.maxEdgeLength = 1.0;
            this.minEdgeLength = 0.3;
            this.minAngle = 5.0;
            this.maxAngle = 170.0;
        }
    }



    partial class Mesh
    {
       class Isct
        {
            public Ray3 ray = new Ray3();
            public bool exists;

            public int tri_id;
            public Vec3 isct = new Vec3();
            public Vec3 bary = new Vec3();
        }
       public class Tri
        {
            public CorkTriangle data = new CorkTriangle();

            public int a;
            public int b;
            public int c;
            public int this[int i]
            {
                get
                {
                    if (i == 0) return this.a;
                    else if (i == 1) return this.b;
                    else return this.c;
                }
                set
                {
                    if (i == 0) this.a = value;
                    else if (i == 1) this.b = value;
                    else this.c = value;
                }
            }
        }

        private class EGraphEntry<T>
        {
            public int vid;
            public List<int> tids = new List<int>();
            public T data;
            public EGraphEntry()
            {
            }
            public EGraphEntry(int vid_)
            {
                this.vid = vid_;
            }
        }


        private class NeighborEntry
        {
            public int vid;
            public List<int> tids = new List<int>();
            public NeighborEntry()
            {
            }
            public NeighborEntry(int vid_)
            {
                this.vid = vid_;
            }
        }
        private class NeighborCache
        {
            public List<List<NeighborEntry>> skeleton = new List<List<NeighborEntry>>();
            public NeighborEntry functorMethod(int i, int j)
            {
                int N = skeleton[i].Count;
                for (int k = 0; k < N; k++)
                {
                    if (skeleton[i][k].vid == j)
                    {
                        return skeleton[i][k];
                    }
                }
                skeleton[i].Add(new NeighborEntry(j));
                return skeleton[i][N];
            }
        }

        internal void resolveIntersections()
        {
            var iproblem = new IsctProblem(this);

            iproblem.findIntersections();

            iproblem.resolveAllIntersections();

            iproblem.commit();

           // iproblem.print();
        }

        internal void disjointUnion(Mesh cp)
        {
            int oldVsize = verts.size();
            int oldTsize = tris.size();
            int cpVsize = cp.verts.size();
            int cpTsize = cp.tris.size();
            int newVsize = oldVsize + cpVsize;
            int newTsize = oldTsize + cpTsize;

            var v_remap = new List<int>().makeSize(cpVsize); // oh this is obvious...
            verts.resize(newVsize);
            tris.resize(newTsize);

            for (int i = 0; i < cpVsize; i++)
                verts[oldVsize + i] = cp.verts[i];

            for (int i = 0; i < cpTsize; i++)
            {
                var tri = cp.tris[i];
                tri.a += oldVsize;
                tri.b += oldVsize;
                tri.c += oldVsize;
                tris[oldTsize+i]=tri;
            }
        }

        private class EGraphCache<T>
        {
            public List<List<EGraphEntry<T>>> skeleton = new List<List<EGraphEntry<T>>>();
            public EGraphEntry<T> functor(int i, int j)
            {
                int N = skeleton[i].Count;
                for (int k = 0; k < N; k++)
                {
                    if (skeleton[i][k].vid == j)
                    {
                        return skeleton[i][k];
                    }
                }
                skeleton[i].Add(new EGraphEntry<T>(j));
                return skeleton[i][N];
            }
            public void for_each(Action<int, int, EGraphEntry<T>> action)
            {
                for (int i = 0; i < skeleton.Count; i++)
                {
                    foreach (var entry in skeleton[i])
                    {
                        action(i, entry.vid, entry);
                    }
                }
            }
        }



        internal List<Tri> tris = new List<Tri>();
        public List<CorkVertex> verts = new List<CorkVertex>();
        public RemeshOptions remesh_options = new RemeshOptions();
        public Mesh() { }
        public Mesh(RawMesh raw)
        {
            verts.AddRange(raw.vertices);
            for (int i = 0; i < raw.triangles.Count; i++)
            {
                var tri = new Tri();
                tri.data = raw.triangles[i];
                tri.a = raw.triangles[i].a;
                tri.b = raw.triangles[i].b;
                tri.c = raw.triangles[i].c;
                tris.Add(tri);
            }
        }

        public int numVerts()
        {
            return verts.Count;
        }

        public int numTris()
        {
            return tris.Count;
        }

        public void boolUnion(Mesh rhs)
        {

            var bprob = new BoolProblem(this);

            bprob.doSetup(rhs);

            bprob.doDeleteAndFlip((data) =>
            {
                if ((data & 2) == 2)     // part of op 0/1 INSIDE op 1/0
                    return TriCode.DELETE_TRI;
                else                    // part of op 0/1 OUTSIDE op 1/0
                    return TriCode.KEEP_TRI;
            });

        }

        public void boolDiff(Mesh rhs) { }
        public void boolIsct(Mesh rhs) { }
        public void boolXor(Mesh rhs) { }


        public RawMesh GetRaw()
        {
            RawMesh result = new RawMesh();
            result.vertices = verts;
            result.triangles = new List<CorkTriangle>().makeSize(tris.Count);
            for (int i = 0; i < tris.Count; i++)
            {
                result.triangles[i] = tris[i].data;
                result.triangles[i].a = tris[i].a;
                result.triangles[i].b = tris[i].b;
                result.triangles[i].c = tris[i].c;
            }
            return result;
        }

        public void for_verts(Action<CorkVertex> func)
        {
            foreach (var v in verts)
            {
                func(v);
            }
        }


        public void for_tris(Action<CorkTriangle, CorkVertex, CorkVertex, CorkVertex> func)
        {
            foreach (var tri in tris)
            {
                var a = verts[tri.a];
                var b = verts[tri.b];
                var c = verts[tri.c];
                func(tri.data, a, b, c);
            }
        }

         void accessIsct<VertData, TriData>(Isct isct, Action<CorkTriangle, CorkVertex, CorkVertex, CorkVertex> func)
        {
            Tri tri = tris[isct.tri_id];
            var a = verts[tri.a];
            var b = verts[tri.b];
            var c = verts[tri.c];
            func(tri.data, a, b, c);
        }

        internal bool isSelfIntersecting()
        {
            //IsctProblem iproblem(this);

            //return iproblem.hasIntersections();
            return false;
        }

        public bool isClosed()
        {
            EGraphCache<int> chains = createEGraphCache<int>();
            chains.for_each((i, j, entry) =>
            {
                entry.data = 0;
            });
            // count up how many times each edge is encountered in one
            // orientation vs. the other
            for (int ind = 0; ind != tris.Count; ++ind)
            {
                chains.functor(tris[ind].a, tris[ind].b).data++;
                chains.functor(tris[ind].b, tris[ind].a).data--;

                chains.functor(tris[ind].b, tris[ind].c).data++;
                chains.functor(tris[ind].c, tris[ind].b).data--;

                chains.functor(tris[ind].c, tris[ind].a).data++;
                chains.functor(tris[ind].a, tris[ind].c).data--;
            }
            // now go through and see if any of these are non-zero
            bool closed = true;
            chains.for_each((i, j, entry) =>
            {
                if (entry.data != 0)
                    closed = false;
            });
            return closed;
        }

        private EGraphCache<T> createEGraphCache<T>()
        {
            EGraphCache<T> result = new EGraphCache<T>();
            result.skeleton.resize(verts.size(),()=>new List<EGraphEntry<T>>());

            for (int tid = 0; tid < tris.Count; tid++)
            {
                var tri = tris[tid];

                result.functor(tri.a, tri.b).tids.Add(tid);
                result.functor(tri.b, tri.a).tids.Add(tid);

                result.functor(tri.a, tri.c).tids.Add(tid);
                result.functor(tri.c, tri.a).tids.Add(tid);

                result.functor(tri.b, tri.c).tids.Add(tid);
                result.functor(tri.c, tri.b).tids.Add(tid);
            }

            return result;
        }

       public void subdivide_tri( int t_piece_ref, int t_parent_ref )
        {
            SubdivideTriInput input = new SubdivideTriInput();
            input.pt = tris[t_parent_ref].data;
            for (int k = 0; k < 3; k++)
            {
                input.pv[k] = (verts[tris[t_parent_ref][k]]);
                input.v[k] = (verts[tris[t_piece_ref][k]]);
            }
            tris[t_piece_ref].data.subdivide(input);
        }

    }


}
